package net.javlov;

/**
 * Class that provides a basic implementation of the ContinuousAction interface. Subclasses only
 * need to implement {@link #getMaxRange()}, {@link #getMinRange()}, and {@link #execute(Agent)}.
 * 
 * @author Matthijs Snel
 *
 */
public abstract class AbstractContinuousAction implements ContinuousAction {

	private static final long serialVersionUID = 6695367929452993940L;
	private static int counter = 0;
	
	/**
	 * The values the action is currently holding.
	 */
	protected double[] values;
	
	/**
	 * Dimensionality of the action. Subclasses need to set this.
	 */
	protected int dim;
	
	private int id;
	
	protected AbstractContinuousAction() {
		id = counter++;
	}
	
	protected AbstractContinuousAction(double[] values) {
		this();
		this.values = values;
	}
	
	/**
	 * @return the dimensionality of the action (how many values it requires)
	 */
	@Override
	public int getDimensionality() {
		return dim;
	}
	
	@Override
	public int getID() {
		return id;
	}
	
	@Override
	public void setID(int id) {
		this.id = id;
	}
	
	/**
	 * Returns a reference to the values the action is currently holding.
	 * @return the values the action is currently holding.
	 */
	@Override
	public double[] getValues() {
		return values;
	}
	
	/**
	 * Sets the dimensionality of the action (how many values it requires).
	 * @param dimensionality the dimensionality.
	 */
	protected void setDimensionality(int dimensionality) {
		dim = dimensionality;
	}
	
	/**
	 * Sets the action values to the specified values.
	 * 
	 * @param vals the values to set the action to.
	 * @throws IllegalArgumentException if the length of the specified array of values does not
	 * match the dimensionality of the action.
	 */
	@Override
	public void setValues(double[] vals) {
		if ( vals.length != dim )
			throw new IllegalArgumentException("Required " + dim + " values but received:" + vals.length);
		values = vals;
	}
	
	/**
	 * Compares this object against the specified object. The result is true if and only if the
	 * argument is not null and is a {@code AbstractContinuousAction} of the same class as the
	 * class of this object, of which the value array
	 * has the same length as the array of this object, and all values in the arrays are equal
	 * according to the method specified in {@link Double#equals(Object)}.
	 * 
	 * Note that it usually doesn't make much sense to compare double values for equality, so unless
	 * the double values in the array actually represent integers, or only a discrete set of
	 * values (e.g. either 0, 0.25, 0.5, 0.75 or 1), this method will usually return false.
	 * 
	 * @param the object to compare with
	 * @return {@code true} if the objects are the same; {@code false} otherwise.
	 */
	@Override
	public boolean equals( Object o ) {
		if ( o == null || !(o instanceof AbstractContinuousAction) )
			return false;
		if ( !getClass().equals(o.getClass()) )
			return false;
		AbstractContinuousAction act = (AbstractContinuousAction) o;
		if ( values.length == act.values.length)
			for ( int i = 0; i < values.length; i++ )
				if ( !(Double.doubleToLongBits(values[i]) == Double.doubleToLongBits(act.values[i])) )
					return false;
		return true;
	}
	
	/**
	 * Returns the hash code for this continuous action, according to the methods specified in 
	 * {@link List} and {@link Double}.
	 */
	@Override
	public int hashCode() {
		long v = 0;
		int hashCode = 1;
		for ( int i = 0; i < values.length; i++ ) {
			v = Double.doubleToLongBits(values[i]);
			hashCode = 31*hashCode + (int)(v^(v>>>32));
		}
		hashCode = 31*hashCode + getClass().hashCode();
		return hashCode;
	}
	
	@Override
	public String toString() {
		String s = getClass().toString() + ": [";
		for ( int i = 0; i < values.length; i++ ) {
			s += values[i] + " ";
		}
		s += "]";
		return s;
	}
}
